package com.gitee.wsl.unit.angle.ext

import com.gitee.wsl.text.format.format
import com.gitee.wsl.unit.angle.AngleUnit
import kotlin.math.floor
import kotlin.math.round
import kotlin.math.sign

/**
 * Obtain an angle from a given number of positive degrees, minutes and seconds.
 *
 * @param degrees integer number of degrees, positive.
 * @param minutes integer number of minutes, positive only between 0 and 60.
 * @param seconds integer number of seconds, positive only between 0 and 60.
 *
 * @return a new angle whose size in degrees is given by `degrees`, `minutes` and `seconds`.
 *
 * @throws IllegalArgumentException if minutes or seconds are outside the 0-60 range or the degrees is negative.
 */
fun AngleUnit.Companion.fromDMS(degrees: Int, minutes: Int, seconds: Double): AngleUnit {
    require(degrees >= 0) {
        //logMessage(ERROR, "Angle", "fromDMS", "invalidDegrees")
    }
    require(minutes in 0 until 60) {
        //logMessage(ERROR, "Angle", "fromDMS", "invalidMinutes")
    }
    require(seconds >= 0 && seconds < 60) {
        //logMessage(ERROR, "Angle", "fromDMS", "invalidSeconds")
    }
    return fromDegrees(degrees + minutes / 60.0 + seconds / 3600.0)
}


/**
 * Obtain an angle from a degrees, minute and seconds character string.
 *
 * eg:<pre>
 * 123 34 42
 * -123* 34' 42" (where * stands for the degree symbol)
 * +45* 12' 30" (where * stands for the degree symbol)
 * 45 12 30 S
 * 45 12 30 N
 * </pre>
 * For a string containing both a sign and compass direction, the compass direction will take precedence.
 *
 * @param dmsString the degrees, minute and second character string.
 *
 * @return the corresponding angle.
 *
 * @throws IllegalArgumentException if dmsString is not properly formatted.
 */
fun AngleUnit.Companion.fromDMS(dmsString: String): AngleUnit {
    var dms = dmsString
    // Check for string format validity
    val regex = Regex("([-+]?\\d{1,3}[dD°\\s](\\s*\\d{1,2}['’\\s])?(\\s*\\d{1,2}[\"”\\s])?\\s*([NnSsEeWw])?\\s?)")
    require(regex.matches("$dms ")) {
        //logMessage(ERROR, "Angle", "fromDMS", "invalidFormat")
    }
    // Replace degree, min and sec signs with space
    dms = dms.replace("[Dd°'’\"”]".toRegex(), " ")
    // Replace multiple spaces with single ones
    dms = dms.replace("\\s+".toRegex(), " ")
    dms = dms.trim { it <= ' ' }

    // Check for sign prefix and suffix
    var sign = 1
    val suffix = dms.uppercase()[dms.length - 1]
    val prefix = dms[0]
    if (!suffix.isDigit()) {
        sign = if (suffix == 'S' || suffix == 'W') -1 else 1
        dms = dms.substring(0, dms.length - 1)
        dms = dms.trim { it <= ' ' }

        // check and trim the prefix if it is erroneously included
        if (!prefix.isDigit()) {
            dms = dms.substring(1, dms.length)
            dms = dms.trim { it <= ' ' }
        }
    } else if (!prefix.isDigit()) {
        sign *= if (prefix == '-') -1 else 1
        dms = dms.substring(1, dms.length)
    }

    // Extract degrees, minutes and seconds
    val dmsArray = dms.split(" ")
    val d = dmsArray[0].toInt()
    val m = if (dmsArray.size > 1) dmsArray[1].toInt() else 0
    val s = if (dmsArray.size > 2) dmsArray[2].toDouble() else 0.0
    return AngleUnit.fromDMS(d, m, s) * sign.toDouble()
}

/**
 * Obtains a [String] representation of this [Angle] formatted as degrees, minutes and seconds.
 *
 * @param digits the number of digits past the decimal point to include in the string.
 *
 * @return the value of this angle in degrees, minutes and seconds as a string.
 */
fun AngleUnit.toDMSString(digits: Int = 1): String {
    val dms = toDMS()
    return String.format("${if (dms[0] < 0) "-" else ""}%d° %2d’ %2.${digits}f”",dms[1], dms[2], dms[3])
}

fun AngleUnit.toDMS(): DoubleArray {
    var angle = degrees
    val sign = sign(angle)
    angle *= sign
    var d = floor(angle)
    angle = (angle - d) * 60.0
    var m = floor(angle)
    angle = (angle - m) * 60.0
    var s = round(angle * 100) / 100 // keep two decimals for seconds
    if (s == 60.0) {
        m++
        s = 0.0
    }
    if (m == 60.0) {
        d++
        m = 0.0
    }
    return doubleArrayOf(sign, d, m, s)
}